iT邦幫忙

2026 iThome 鐵人賽

DAY 30
0
Software Development

從登入到授權-現代軟體的身分架構指南系列 第 30 篇

Day 30|安全與效能如何取捨:雜湊成本、Token 驗證、快取與限流

  • 分享至 

  • xImage
  •  

前言

昨天從重放攻擊、時序攻擊與競態條件,說明驗證流程不能只檢查「資料是否正確」,還要確認它是否屬於這次流程、是否已經使用過,以及狀態更新與比較過程是否安全。

但這些防護都有成本:密碼雜湊需要運算資源,Token 驗證可能需要查詢伺服器,權限判斷與防重放紀錄也需要讀寫資料。如果每一層都加檢查,登入與 API 會不會越來越慢?若為了速度把結果快取起來,又會不會讓已經撤銷的權限繼續生效?

本篇的核心不是「安全與效能只能選一個」,而是:先定義不能省略的安全要求,再找出哪些成本可以透過參數、快取與架構設計來降低。

今天內容涵蓋:

  1. 安全與效能,究竟在取捨什麼
  2. 密碼雜湊
  3. Token 驗證
  4. 快取
  5. 限流

一、安全與效能,究竟在取捨什麼

登入變慢時,最容易出現的念頭是「這個檢查可以拿掉嗎?」。但密碼雜湊是在抵抗外洩後的猜測,簽章驗證是在確認資料沒被篡改,權限查詢是在確認這個人現在還能不能做這件事。拿掉它們確實會變快,只是那不叫優化,而是改了系統願意相信什麼。

接下來的雜湊成本、Token 驗證、快取與限流,每一項都有不同做法,也沒有一組套到哪裡都對的數字;真正決定怎麼選的,是這個系統自己的風險與需求。


二、密碼雜湊

攻擊者取得密碼雜湊資料後,可以在自己的設備上不斷猜測,不必再經過網站的登入端點;這種 離線猜測 不受網站登入限流的限制。因此密碼雜湊需要讓每一次猜測都付出足夠的 CPU 或記憶體成本,Argon2id 等演算法就是為此設計的。

但同樣的成本也會出現在合法登入上。如果參數設得太高,大量同時登入或惡意嘗試,可能耗盡驗證伺服器的 CPU、記憶體或工作佇列。

NIST SP 800-63B-4 的原則,是在驗證服務可承受的前提下選擇較高的成本,並隨運算能力提升逐步調整。像 OWASP 對 Argon2id 就列出過一組最低建議配置(記憶體成本、迭代次數與平行度),但那是參考最低標準,不是所有系統的最佳解。OWASP Password Storage Cheat Sheet 也提醒,理想參數需要在實際部署環境中測試,而不是直接複製別人的數字。

不是每次 API 請求都重新驗證密碼

在使用 Session 或 OAuth Token 的架構中,密碼驗證應放在需要它的登入流程;後續 API 則依 Session 或 Token 的規則驗證請求,不必每次都要求使用者重新送出密碼並執行密碼雜湊。

真正的優化,是把不同成本放在正確的流程階段,而不是把必要的保護拿掉。


三、Token 驗證

使用者登入或完成授權之後,API 仍然需要判斷每次請求帶來的 Access Token 是否可以接受。這時常見有兩種處理方式。

本地驗證 JWT

如果 Access Token 是可驗證的 JWT,Resource Server 可以使用可信的金鑰,檢查簽章與必要欄位,通常不必為了每次 Token 驗證都呼叫 Authorization Server。

它能降低網路往返與授權伺服器的負擔,但要注意:本地驗證通常確認的是這顆 Token 帶來的資訊,不是所有外部狀態的最新版本。

例如,Token 核發後,使用者的角色可能已經被移除。如果 API 只相信 Token 裡原本寫好的角色,就不會因為資料庫已更新,自動知道這項變更。

透過 Introspection 查詢狀態

RFC 7662 定義的 Token Introspection,讓 Resource Server 透過受保護且經授權的查詢,向 Authorization Server 確認 Token 狀態與相關資訊。

查詢的回應裡有一個 active 欄位,代表 Authorization Server 現在認不認這顆 Token:是不是自己發的、有沒有過期、是不是已經被撤銷。

比起 API 自己檢查簽章,這樣拿到的狀態更接近當下狀態,但每次請求都多一趟網路查詢,也多依賴一個服務;若將回應快取以減少查詢次數,取得的狀態則會出現延遲,此部分將於後續說明。

比較項目 本地驗證 JWT Token Introspection
主要成本 本地密碼學運算與欄位檢查 網路往返、伺服器查詢與回應處理
對發行者服務的依賴 已取得可信金鑰時,通常不必每次連線;仍需處理金鑰更新 未使用可接受的快取結果時,需要取得查詢回應
狀態新鮮度 單靠 JWT 內容,無法自動得知核發後的撤銷或權限變更 依發行者掌握的最新狀態與快取策略而定
常見使用情境 JWT 型 Access Token,可在 API 端驗證 常用於不透明 Token,也可以查詢支援此機制的 JWT

這裡比較的是查核方式,不是兩種完全互斥的 Token 分類。JWT 是格式,Introspection 是查詢協定;是否能搭配,必須看 Authorization Server 的支援。

不論選哪一種,API 的判斷不能消失

本地驗證並非僅將 JWT 解碼為 JSON。不論採用哪一種查核方式,都可以回頭確認幾件事:這顆 Token 是否來自允許的發行來源?演算法、有效期限與受眾是否已經檢查?而在接受這顆 Token 之後,該主體是否確實被允許存取這筆資源?RFC 8725 針對演算法、Issuer、Audience 與不同 JWT 用途均提出明確建議。需要注意的是,Token 狀態與資源授權屬於不同層次的判斷。active: true 只表示這顆 Token 本身仍可接受,不代表這次操作已經獲准;使用者的 Token 雖未過期也未被撤銷,仍須再判斷他能否讀取這份資料或執行該項管理操作。

短效 Token 也有成本

縮短 Access Token 的有效期限可以限制外洩後的使用窗口,但也會增加刷新次數與 Authorization Server 的負載,而且 Refresh Token 本身仍需保護。另外,登出、停用帳號或撤銷 Refresh Token,不代表已核發的 JWT Access Token 立即失效;若業務要求快速撤權,必須另外規劃 Resource Server 如何取得並執行這項變更。

所以這裡並沒有哪一種天生比較好,而是取決於兩項評估:該系統對狀態新鮮度的要求,以及可承受的查詢成本。


四、快取

快取不只是把資料放到 Redis 或記憶體裡。放在驗證與授權流程中,它代表:系統願意在一段時間內,繼續相信先前取得的資訊。

因此這裡涉及兩個層面:快取的對象為何,以及快取的有效期間應該多長。

快取對象 可節省的成本 必須處理的風險
JWKS 公開金鑰與解析後的金鑰物件 重複下載與處理驗證金鑰 金鑰輪替、來源信任與緊急失效
Introspection 回應 Token 狀態查詢與網路往返 Token 已撤銷,API 卻仍使用舊的有效結果
權限資料或授權結果 資料庫、政策或關係查詢 角色、資源關係或環境條件已改變
敏感 API 回應 重複查詢與組合資料的成本 不同使用者或租戶之間共用到不該共用的資料

驗證金鑰的快取

JWKS(JSON Web Key Set)是金鑰集合的表示格式;在這裡,指發行者提供、用來驗證 Token 簽章的公開金鑰集合。

以非對稱式簽章的 JWT 為例,一種做法是快取發行者的公開金鑰;AWS Cognito 官方文件也建議快取驗證金鑰,並處理定期更新與金鑰輪替。

一旦採用快取,隨之而來的是幾項需要自行決定的問題:金鑰應從何種來源取得才具可信度、金鑰輪替或出現未知 kid 時是否重新下載、下載失敗時應拒絕請求或仍予放行,以及過去的驗證成功紀錄是否能作為本次驗證的依據。

快取有效,不代表 Token 狀態沒有改變

假設 API 在 12:00 查到 active: true 並快取兩分鐘,而 Authorization Server 在 12:01 撤銷了這顆 Token,那麼在快取到期前,API 仍可能繼續接受請求。

RFC 7662 明確指出,快取 Introspection 回應屬於效能與資訊新鮮度之間的取捨;若回應包含 exp,快取時間也不得超過該時限。

因此,TTL 的設定實際上是一項業務問題:該系統可以容忍已撤銷的權限繼續生效多久。若帳號同步、Token 狀態與權限資料各自存在延遲,需評估的也不是單一層的 TTL,而是整體流程的生效時間。

授權快取的索引不能僅依使用者身分

授權結果不只取決於帳號,還可能與角色、租戶、資源、操作、關係及環境條件相關。若某次判斷的前提是「使用者可讀取自己的薪資」,卻僅以使用者 ID 將結果快取為 Allow,則在讀取他人薪資時可能誤用該結果。

因此,授權快取的索引應包含哪些條件,以及權限或政策變更時如何使快取失效,實際上比快取期間的長短更值得先行釐清。

HTTP 回應快取是另一層設定

針對薪資明細等敏感回應,可依實際需求採用以下標頭,要求 HTTP 快取不得保存回應內容:

Cache-Control: no-store

需要注意的是,no-store、no-cache 與 private 的語意並不相同,且這些標頭僅適用於 HTTP 快取,並不涵蓋應用程式內部的 Redis、資料庫或 Log。即使已設定相關標頭,後端的存取控制與敏感資料遮罩仍須另行處理。

⚠️Replay Cache 雖然叫快取,但不是為了加速而存在。

它記錄的是「哪些一次性憑證已經被用過」,屬於安全狀態。若為了釋放記憶體而提早刪除這些紀錄,或在儲存服務故障時直接視為「未曾使用」而予以放行,同一份請求就可能被重複送出並通過驗證。


五、限流

密碼雜湊提高了猜測的難度,但若攻擊者可以不受限制地呼叫登入端點,這些昂貴的運算反而可能被用來耗盡服務資源。Rate Limiting(限流)是指限制一定時間內可執行的請求或操作次數,既避免少數來源佔用過多資源,也降低線上密碼猜測與驗證碼濫用的機會。惟各端點的成本與風險並不相同,「所有 API 每分鐘幾次」這類單一設定是否能對應實際風險,仍有討論空間。

要限制的不只是 IP

控制範圍 適合處理的問題 注意事項
來源 IP、連線與入口流量 大量無效請求、連線或異常輸入 共用出口可能誤傷正常使用者,分散來源也可能繞過單一 IP 限制
目標帳號的登入失敗次數 多個來源集中猜同一個帳號 避免被利用來永久鎖住別人的帳號
已驗證的使用者、Client 或租戶 API 配額與公平使用 不能只相信外部自行填寫的識別碼,或未驗證 JWT 裡的欄位
特定高成本操作 匯出、複雜查詢、OTP/簡訊寄送 同時限制操作頻率、併發量與實際資源成本

若僅以來源 IP 計算登入失敗次數,攻擊者只要更換 IP 就可以繼續嘗試,因此 OWASP Authentication Cheat Sheet 建議失敗計數也要依帳號計算。但該文件同時提醒,以帳號為單位的鎖定機制可能被刻意觸發:攻擊者持續輸錯某個帳號的密碼,就能把這個帳號鎖住,讓本人也登不進去。

兩者合看,需要自行權衡的是:錯幾次才要處理、冷卻多久,以及鎖定後要用什麼方式解除,才能擋住密碼猜測,又不至於把帳號本人擋在外面。另一個容易被忽略的是配額的計數單位:若「每分鐘幾次」是綁在單一 Access Token 上,使用者只要重新登入換一顆新 Token,計數是不是就歸零了?

請求數量,不等於資源消耗量

只查詢自己的基本資料,與匯出整個部門的薪資,雖然都算一次 HTTP 請求,但成本卻可能差很多。

OWASP API Security Top 10 的 API4:2023 把這類風險整理為 Unrestricted Resource Consumption(資源消耗未受限制)。所以除了請求頻率,也值得回頭看看:單次請求能讀多少資料、工作可以跑多久、同時能跑幾個,以及簡訊或第三方服務的用量與費用由誰把關。已經通過驗證,不代表資源使用可以沒有上限。

精確度與分散式成本也需要取捨

以「每分鐘最多 100 次」為例,若採用整分鐘重新計數的固定窗口,同一來源可以在窗口結束前送完 100 次,下一秒進入新窗口再送 100 次;這種方式實作簡單,卻可能在窗口交界出現集中突發。滑動窗口改以「過去一分鐘」計算,Token Bucket 則讓額度依固定速率逐步補充,兩者都是在處理同一個問題,適用與否仍需依實際負載型態評估。

分散部署時還有另一層問題:若三台 API Server 各自在本機計數,設定上雖為 100 次,實際可能放行到 300 次。若需要精確的配額,就必須將計數改放在共享儲存,並以原子性操作更新,避免多台同時讀到相同數值,代價則是每次請求都多一趟網路成本。

常見的做法是在入口層先擋下明顯過量的流量,再由應用層依使用者與操作成本進一步區分。不過配額多放行一兩次通常影響有限,Authorization Code 與 OTP 則只能使用一次,多放行一次就等於留下重放的空間;這類一次性規則能否沿用相同的誤差容忍度,屬於另一層需要釐清的問題。

超過限制時,要讓 Client 知道怎麼處理

RFC 6585 定義了 429 Too Many Requests,並允許使用 Retry-After 告知 Client 等待時間。例如:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30
Cache-Control: no-store

{
  "error": "rate_limit_exceeded",
  "message": "請稍後再試。"
}

這裡的 30 秒只是示意,實際值取決於限流策略與額度恢復的情況。另一個連帶的問題是 Client 的行為,若所有請求在同一刻重送,服務可能在剛恢復時又被壓垮。

限流是在控制使用量,不是用來判斷請求本身是否有權限。請求就算沒有超額,仍然需要通過正常的驗證與授權。


小結

安全與效能的取捨,不是把防護一項一項刪掉,而是理解每項機制的目的,再決定它放在哪裡、使用多少資源,以及多久需要重新確認。本篇可以整理成四個重點:

  • 密碼雜湊: 離線猜測成本與驗證服務的負載是同一組參數的兩面,NIST SP 800-63B-4 的方向是在服務可承受的前提下選較高成本。
  • Token 驗證: 本地驗證與 Introspection 各有成本與新鮮度特性,但都不能取代本次資源的授權判斷。
  • 快取: 公開金鑰、Token 狀態、權限與敏感回應的風險不同,能容忍的延遲與失效方式也不一樣。
  • 限流: 請求次數之外,還有資料量、併發與外部服務費用,OWASP 就把這類風險列為 API4:2023。

若將上述機制放回完整流程,一般受保護 API 的處理順序可以理解為:入口流量控制與輸入限制 → 驗證憑證、時間、用途與必要的持有證明 → 依已驗證主體檢查配額 → 針對本次操作與資源執行授權 → 執行業務邏輯並套用回應快取政策。實際順序可依架構調整,但不宜使「快取命中」「來自內網」或「之前登入成功」成為省略後續判斷的依據。

在優化對象的識別上,實際量測通常比推測可靠:API 效能下降未必來自密碼學運算,也可能源於重複下載 JWKS、重複查詢權限或工作佇列累積。


致謝

會開始整理這一系列,是因為在學習身分驗證與授權相關技術的過程中,我逐漸發現,許多概念單獨理解並不困難,但當它們同時出現在登入、授權與 API 存取流程中時,彼此之間的角色與界線往往容易混淆。

因此,這三十天的內容從最基本的概念出發,逐步延伸至 Session、Token、OAuth 2.0、OIDC、SSO、Passkey,以及不同的存取控制模型。希望不只是介紹各項技術本身,更能釐清它們存在的目的、所解決的問題、適用的情境,以及各自的限制。

如果這一系列能讓讀者在閱讀 OAuth、OIDC、Token、Passkey 或相關技術文件時,對整體脈絡有更清晰的理解,並在實際進行系統設計時,能更有意識地思考驗證、授權與權限檢查之間的關係,那麼這三十天的整理便具有了意義。

最後,感謝每一位閱讀至此的讀者,也感謝這段整理與學習的過程。

謝謝你陪伴這個系列走到最後。


參考資源


上一篇
Day 29|重放攻擊、時序攻擊與競態條件:驗證流程中的時間與狀態陷阱
系列文
從登入到授權-現代軟體的身分架構指南 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言